package nes

import (
	"fmt"
)

func (c *CPU) Trace() string {
	op := CPU_OPS_CODES_MAP[c.Mem_read(c.ProgramCounter)]
	hexDump := []byte{op.Code}

	var memAddr uint16
	var storedValue byte

	switch op.Mode {
	case Immediate, NoneAddressing:
		memAddr = 0
		storedValue = 0
	default:
		memAddr, _ = c.get_absolute_address(op.Mode, c.ProgramCounter+1)
		storedValue = c.Mem_read(memAddr)
	}

	var tmp string
	switch op.Len {
	case 1:
		switch op.Code {
		case 0x0a, 0x4a, 0x2a, 0x6a:
			tmp = "A "
		default:
			tmp = ""
		}
	case 2:
		address := c.Mem_read(c.ProgramCounter + 1)
		hexDump = append(hexDump, address)

		switch op.Mode {
		case Immediate:
			tmp = fmt.Sprintf("#$%02x", address)
		case ZeroPage:
			tmp = fmt.Sprintf("$%02x = %02x", memAddr, storedValue)
		case ZeroPage_X:
			tmp = fmt.Sprintf("$%02x,X @ %02x = %02x", address, memAddr, storedValue)
		case ZeroPage_Y:
			tmp = fmt.Sprintf("$%02x,Y @ %02x = %02x", address, memAddr, storedValue)
		case Indirect_X:
			tmp = fmt.Sprintf("($%02x,X) @ %02x = %04x = %02x", address, address+c.Register_x, memAddr, storedValue)
		case Indirect_Y:
			tmp = fmt.Sprintf("($%02x),Y = %04x @ %04x = %02x", address, memAddr-uint16(c.Register_y), memAddr, storedValue)
		case NoneAddressing:
			address := c.ProgramCounter + 2 + uint16(int8(address))
			tmp = fmt.Sprintf("$%04x", address)
		default:
			panic(fmt.Sprintf("unexpected addressing mode %v has ops-len 2. code %02x", op.Mode, op.Code))
		}
	case 3:
		addressLo := c.Mem_read(c.ProgramCounter + 1)
		addressHi := c.Mem_read(c.ProgramCounter + 2)
		hexDump = append(hexDump, addressLo, addressHi)

		address := c.Mem_read_u16(c.ProgramCounter + 1)

		switch op.Mode {
		case NoneAddressing:
			if op.Code == 0x6c {
				jmpAddr := c.Mem_read_u16(address)
				tmp = fmt.Sprintf("($%04x) = %04x", address, jmpAddr)
			} else {
				tmp = fmt.Sprintf("$%04x", address)
			}
		case Absolute:
			tmp = fmt.Sprintf("$%04x = %02x", memAddr, storedValue)
		case Absolute_X:
			tmp = fmt.Sprintf("$%04x,X @ %04x = %02x", address, memAddr, storedValue)
		case Absolute_Y:
			tmp = fmt.Sprintf("$%04x,Y @ %04x = %02x", address, memAddr, storedValue)
		default:
			panic(fmt.Sprintf("unexpected addressing mode %v has ops-len 3. code %02x", op.Mode, op.Code))
		}
	default:
		tmp = ""
	}

	hexStr := ""
	for _, b := range hexDump {
		hexStr += fmt.Sprintf("%02x ", b)
	}

	asmStr := fmt.Sprintf("%04x  %-8s %-4s %s", c.ProgramCounter, hexStr, op.Mnemonic, tmp)
	asmStr = asmStr[:len(asmStr)-1] // Trim trailing space

	return fmt.Sprintf("%-47s A:%02x X:%02x Y:%02x P:%02x SP:%02x",
		asmStr, c.Register_a, c.Register_x, c.Register_y, c.Status, c.StackPointer)
}
